﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using RFEngine.Audio;
using RFEngine.Entities;
using RFEngine.Graphics;
using RFEngine.Graphics.Interfaces;

namespace RFEngine
{
    public sealed class EntityManager
    {
        #region Fields

        private static List<Song> _Songs = new List<Song>();
        private static List<ISound> _Sounds = new List<ISound>();
        private static List<Camera> _Cameras = new List<Camera>();
        private static List<CameraLayer> _CameraLayers = new List<CameraLayer>();

        private static Camera _DefaultCamera;

        #endregion

        #region Methods

        #region Constructor(s)

        internal EntityManager() 
        {
            _DefaultCamera = new Camera(EngineServices.GraphicsDevice);
        }

        #endregion

        #region Static Methods

        #region Audio

        /// <summary>
        /// Method for Adding a new song to the EntityManager.
        /// </summary>
        /// <param name="name">The name of the new instance.</param>
        /// <returns>Returns a new Song instance with the given name.</returns>
        public static Song AddSong(string name)
        {
            _Songs.Add(new Song(name));

            return _Songs.Last();
        }

        /// <summary>
        /// Method for adding a new sound to the Audio Manager.
        /// </summary>
        /// <typeparam name="T">The type of ISound to add to the manager.</typeparam>
        /// <param name="_Sound">The _Sound instance to add.</param>
        public static void AddSound<T>(T sound) where T : ISound
        {
            if (sound.GetType() != typeof(Track))
                _Sounds.Add(sound);
        }

        /// <summary>
        /// Method for Removing a song from the AudioManager. Calling this method for a song that is 
        /// actively maintained by this Manager will destroy the entity.
        /// </summary>
        /// <param name="songToRemove">The song to remove from the manager.</param>
        public static void RemoveSong(Song songToRemove)
        {
            if (_Songs.Remove(songToRemove))
            {
                songToRemove.Destroy();
                songToRemove = null;
            }
        }

        /// <summary>
        /// Method for removing a _Sound from the AudioManager. Calling this method for a _Sound that is
        /// actively maintained by this Manager will destroy the entity.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="soundToRemove"></param>
        public static void RemoveSound<T>(T soundToRemove) where T : ISound
        {
            if (_Sounds.Remove(soundToRemove))
                soundToRemove.Destroy();
        }

        #endregion

        #region Graphics

        #region Sprites

        /// <summary>
        /// Method for adding a new sprite to the EntityManager.
        /// </summary>
        /// <param name="textureLocation">The file path location to the texture for the new sprite</param>
        /// <returns>Returns a new sprite instance.</returns>
        public static Sprite AddSprite(string textureLocation)
        {
            return AddSprite(textureLocation, EngineServices.GlobalContentManagersName);
        }

        /// <summary>
        /// Method for adding a new sprite to the EntityManager.
        /// </summary>
        /// <param name="textureLocation">The file path location to the texture for the new sprite</param>
        /// <param name="contentManagersName">The contentmanagersname to use when creating a new sprite instance.</param>
        /// <returns>Returns a new sprite instance.</returns>
        public static Sprite AddSprite(string textureLocation, string contentManagersName)
        {
            return AddSprite(textureLocation, contentManagersName, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new sprite to the EntityManager.
        /// </summary>
        /// <param name="textureLocation">The file path location to the texture for the new sprite</param>
        /// <param name="contentManagersName">The contentmanagersname to use when creating a new sprite instance.</param>
        /// <param name="layer">The layer to add the newly created sprite to.</param>
        /// <returns>Returns a new sprite instance.</returns>
        public static Sprite AddSprite(string textureLocation, string contentManagersName, CameraLayer layer)
        {
            Sprite sprite = new Sprite(EngineServices.GetContentManagerByName(contentManagersName).Load<Texture2D>(@textureLocation));
            AddSprite(sprite);
            return sprite;
        }

        /// <summary>
        /// Method for adding a new sprite to the EntityManager.
        /// </summary>
        /// <param name="item"></param>
        public static void AddSprite(Sprite item)
        {
            AddSprite(item, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new sprite to the EntityManager.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="layer"></param>
        public static void AddSprite(Sprite item, CameraLayer layer)
        {
            AddEntity(item, layer);
        }

        /// <summary>
        /// Method for removing the sprite item from the Entity Manager.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        public static void RemoveSprite(Sprite item)
        {
            _CameraLayers.ForEach((layer) => layer.RemoveEntity(item));
        }

        /// <summary>
        /// Method for removing multiple sprites from the manager.
        /// </summary>
        /// <param name="collection">A Collection of sprites used to remove from the manager.</param>
        public static void RemoveSprites(List<Sprite> collection)
        {
            for (int i = 0; i < collection.Count; i++)
                RemoveSprite(collection[i]);
        }

        /// <summary>
        /// Method for removing multiple sprites from the manager.
        /// </summary>
        /// <param name="collection">A Collection of sprites used to remove from the manager.</param>
        public static void RemoveSprites(Sprite[] collection)
        {
            for (int i = 0; i < collection.Length; i++)
                RemoveSprite(collection[i]);
        }

        #endregion

        #region Text

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="fontLocation">The file path location to the font to use</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <returns>Returns a new text instance.</returns>
        public static Text AddText(string fontLocation, string displayText)
        {
            return AddText(fontLocation, EngineServices.GlobalContentManagersName, displayText, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="fontLocation">The file path location to the font to use</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <param name="layer">The camera layer to add the text entity to</param>
        /// <returns>Returns a new text instance.</returns>
        public static Text AddText(string fontLocation, string displayText, CameraLayer layer)
        {
            return AddText(fontLocation, EngineServices.GlobalContentManagersName, displayText,  layer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="fontLocation">The file path location to the font to use</param>
        /// <param name="contentManagersName">The name of the contentManager to use to load the font from</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <returns>Returns a new text instance</returns>
        public static Text AddText(string fontLocation, string contentManagersName, string displayText)
        {
            return AddText(fontLocation, contentManagersName, displayText, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="fontLocation">The file path location to the font to use</param>
        /// <param name="contentManagersName">The name of the contentManager to use to load the font from</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <param name="layer">The camera layer to add the text entity to</param>
        /// <returns>Returns a new text instance</returns>
        public static Text AddText(string fontLocation, string contentMangersName, string displayText, CameraLayer layer)
        {
            return AddText(EngineServices.GetContentManagerByName(contentMangersName).LoadFont(fontLocation, 64), displayText, layer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="font">The font the new text entity will use</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <returns>Returns a new Text instance.</returns>
        public static Text AddText(Font font, string displayText)
        {
            return AddText(font, displayText, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="font">The font the new text entity will use</param>
        /// <param name="displayText">The text the new Text object will display</param>
        /// <param name="layer">The camera layer to add the text entity to</param>
        /// <returns>Returns a new Text instance.</returns>
        public static Text AddText(Font font, string displayText, CameraLayer layer)
        {
            Text text = new Text(font, displayText);
            AddText(text, layer);
            return text;
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="item">The text item to add to the manager.</param>
        public static void AddText(Text item)
        {
            AddText(item, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding a new text entity to the manager.
        /// </summary>
        /// <param name="item">The text item to add to the manager</param>
        /// <param name="layer">The camera layer to add the entity to.</param>
        public static void AddText(Text item, CameraLayer layer)
        {
            AddEntity(item, layer);
        }

        /// <summary>
        /// Method for removeing a text entity to the manager.
        /// </summary>
        /// <param name="item">The text entity to remove from the manager.</param>
        public static void RemoveText(Text item)
        {
            _CameraLayers.ForEach((layer) => layer.RemoveEntity(item));                
        }

        /// <summary>
        /// Method for removeing a collection of text entities to the manager.
        /// </summary>
        /// <param name="collection">The collection of text entities to remove</param>
        public static void RemoveTexts(List<Text> collection)
        {
            for (int i = 0; i < collection.Count; i++)
                RemoveText(collection[i]);
        }

        /// <summary>
        /// Method for removeing a collection of text entities to the manager.
        /// </summary
        /// <param name="collection">The collection of text entities to remove</param>
        public static void RemoveTexts(Text[] collection)
        {
            for (int i = 0; i < collection.Length; i++)
                RemoveText(collection[i]);
        }

        #endregion

        #region Entities

        /// <summary>
        /// Method for adding an entity to be managed by this manager.
        /// </summary>
        /// <typeparam name="T">Entity where the type if of IUpdateable</typeparam>
        /// <param name="entity">The entity to add to the manager.</param>
        public static void AddEntity<T>(T entity) where T : IUpdateableEntity
        {
            AddEntity(entity, _DefaultCamera.DefaultLayer);
        }

        /// <summary>
        /// Method for adding an entity to be managed by this manager.
        /// </summary>
        /// <typeparam name="T">Entity where the type is of IUpdateableEntity</typeparam>
        /// <param name="entity">The entity to add to the manager.</param>
        /// <param name="layer">The camera layer to add the entity to</param>
        public static void AddEntity<T>(T entity, CameraLayer layer) where T : IUpdateableEntity
        {
            for (int i = 0; i < _CameraLayers.Count; i++)
                if (_CameraLayers[i] == layer)
                    _CameraLayers[i].AddEntity(entity);
        }

        /// <summary>
        /// Method for removing an entity from the manager.
        /// </summary>
        /// <typeparam name="T">Entity where the type is of IUpdateableEntity</typeparam>
        /// <param name="entity"></param>
        public static void RemoveEntity<T>(T entity) where T : IUpdateableEntity
        {
            _CameraLayers.ForEach((layer) => layer.RemoveEntity(entity)); 
        }

        #endregion

        #endregion

        #region Internal Methods

        /// <summary>
        /// Method for adding a Camera layer to this manager
        /// </summary>
        /// <param name="layer"></param>
        internal static void AddCameraLayer(CameraLayer layer)
        {
            if (!_CameraLayers.Contains(layer))
                _CameraLayers.Add(layer);
        }

        /// <summary>
        /// Method for Removing a camera layer from this manager.
        /// </summary>
        /// <param name="layer"></param>
        internal static void RemoveCameraLayer(CameraLayer layer)
        {
            _CameraLayers.Remove(layer);
        }

        #endregion

        #endregion

        #region Internal Methods

        /// <summary>
        /// Method for updating all entites this manager holds references to.
        /// </summary>
        internal void Update()
        {
            #region Audio

            _Songs.ForEach((song) =>
                {
                    song.Tracks.ForEach((track) =>
                    {
                        track.Update();
                    }
                    );
                }
            );

            _Sounds.ForEach((sound) => sound.Update());

            #endregion

            _DefaultCamera.Update(TimeManager.LastUpdateTime);

            _Cameras.ForEach((camera) => camera.Update(TimeManager.LastUpdateTime));

            _CameraLayers.ForEach((layer) => layer.Update());
        }

        /// <summary>
        /// Method for drawing all drawable entitties this manager holds references to.
        /// </summary>
        internal void Draw() 
        {
            _CameraLayers.ForEach((layer) => layer.Draw());
        }

        /// <summary>
        /// Method for destroying this instance of the manager.
        /// </summary>
        internal void Destroy()
        {
            _Songs.Clear();
            _Sounds.Clear();
            _CameraLayers.Clear();
            _Songs = null;
            _Sounds = null;
            _CameraLayers = null;

            GC.SuppressFinalize(this);
        }

        #endregion

        #endregion
    }
}
